using System;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using NUnit.Framework;

namespace CodeBackpack.Plist.Test
{
    [TestFixture]
    public class PropertyListXmlWriterTestFixture
    {
        [Test]
        public void Ctor()
        {
            new PropertyListXmlWriter(TextWriter.Null);
        }

        [Test, ExpectedException(typeof (ArgumentNullException))]
        public void CtorNullArg()
        {
            new PropertyListXmlWriter(null);
        }

        [Test]
        public void WriteDtdDecl()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    new PropertyList().Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                XmlDocumentType docType = plistDoc.DocumentType;
                Assert.IsNotNull(docType);
                Assert.AreEqual("plist", docType.LocalName);
                Assert.AreEqual("-//Apple Computer//DTD PLIST 1.0//EN", docType.PublicId);
                Assert.AreEqual("http://www.apple.com/DTDs/PropertyList-1.0.dtd", docType.SystemId);
            }
        }

        [Test]
        public void WriteEmptyPlist()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    new PropertyList().Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual(1, XPath(plistDoc, "count(/plist)"));
                Assert.AreEqual(0, XPath(plistDoc, "count(/plist/*)"));
            }
        }

        [Test]
        public void WritePlistVersion()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    new PropertyList().Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual(1, XPath(plistDoc, "count(/plist[@version='1.0'])"));
            }
        }

        [Test]
        public void WriteBytes()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist.Bytes = new byte[] {1, 2, 3};
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual(new byte[] {1, 2, 3},
                                Convert.FromBase64String(XPath(plistDoc, "/plist/data").ToString()));
            }
        }

        [Test]
        public void WriteString()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist.String = "hello";
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual("hello", XPath(plistDoc, "/plist/string"));
            }
        }

        [Test]
        public void WriteInt()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist.Int = 42;
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual("42", XPath(plistDoc, "/plist/integer"));
            }
        }

        [Test]
        public void WriteLong()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist.Long = -9000L;
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual("-9000", XPath(plistDoc, "/plist/integer"));
            }
        }

        [Test]
        public void WriteDouble()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist.Double = Math.PI;
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual(Math.PI, XPath(plistDoc, "number(/plist/real)"));
            }
        }

        [Test]
        public void WriteDateTime()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist.DateTime = new DateTime(2007, 12, 30, 0, 30, 1, 333, DateTimeKind.Utc);
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual("2007-12-30T00:30:01.333Z", XPath(plistDoc, "/plist/date"));
            }
        }

        [Test]
        public void WriteTrue()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist.Bool = true;
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual("", XPath(plistDoc, "/plist/true"));
            }
        }

        [Test]
        public void WriteFalse()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist.Bool = false;
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual("", XPath(plistDoc, "/plist/false"));
            }
        }

        [Test]
        public void WriteEmptyList()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = PropertyList.CreateList();
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual(1, XPath(plistDoc, "count(/plist/array)"));
                Assert.AreEqual(0, XPath(plistDoc, "count(/plist/array/*)"));
            }
        }

        [Test]
        public void WriteList()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = PropertyList.CreateList();
                    plist[0].Int = 42;
                    plist[1].String = "hello";
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual("42", XPath(plistDoc, "/plist/array/*[1]"));
                Assert.AreEqual("hello", XPath(plistDoc, "/plist/array/*[2]"));
            }
        }

        [Test]
        public void WriteEmptyDict()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = PropertyList.CreateDict();
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual(1, XPath(plistDoc, "count(/plist/dict)"));
                Assert.AreEqual(0, XPath(plistDoc, "count(/plist/dict/*)"));
            }
        }

        [Test]
        public void WriteDict()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = PropertyList.CreateDict();
                    plist["key1"].Int = 42;
                    plist["key2"].String = "hello";
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual("42", XPath(plistDoc, "/plist/dict/key[text()='key1']/following-sibling::integer"));
                Assert.AreEqual("hello", XPath(plistDoc, "/plist/dict/key[text()='key2']/following-sibling::string"));
            }
        }

        [Test]
        public void CompoundWrite()
        {
            using (StringWriter textWriter = new StringWriter())
            {
                using (PropertyListXmlWriter writer = new PropertyListXmlWriter(textWriter))
                {
                    PropertyList plist = new PropertyList();
                    plist[0].Int = 55;
                    plist[1]["foo"].String = "hello";
                    plist[1]["time"].DateTime = new DateTime(2007, 12, 30, 0, 30, 1, 333, DateTimeKind.Utc);
                    plist[1]["pi"].Double = Math.PI;
                    plist.Save(writer);
                }
                XmlDocument plistDoc = TextToXml(textWriter.ToString());
                Assert.AreEqual(2, XPath(plistDoc, "count(/plist/array/*)"));
                Assert.AreEqual(55, XPath(plistDoc, "number(/plist/array/*[name()='integer' and position()=1])"));
                Assert.AreEqual(1, XPath(plistDoc, "count(/plist/array/*[name()='dict' and position()=2])"));
                Assert.AreEqual("hello", XPath(plistDoc, "/plist/array/dict/key[text()='foo']/following-sibling::string"));
                Assert.AreEqual("2007-12-30T00:30:01.333Z", XPath(plistDoc, "/plist/array/dict/key[text()='time']/following-sibling::date"));
                Assert.AreEqual(Math.PI, XPath(plistDoc, "number(/plist/array/dict/key[text()='pi']/following-sibling::real)"));
            }
        }

        private static Object XPath(XmlDocument doc, String xpath)
        {
            XPathNavigator navigator = doc.CreateNavigator();
            XPathExpression expression = navigator.Compile(xpath);
            Object o = navigator.Evaluate(expression);
            if (o is XPathNodeIterator)
            {
                XPathNodeIterator iterator = (XPathNodeIterator) o;
                Assert.AreEqual(1, iterator.Count, "XPath expression '{0}' did not return exactly one node!", xpath);
                iterator.MoveNext();
                return iterator.Current.TypedValue;
            }
            else
            {
                return o;
            }
        }

        private static XmlDocument TextToXml(string xmlText)
        {
            XmlDocument doc = new XmlDocument();
            doc.XmlResolver = PropertyListXmlReader.DtdResolver;
            doc.LoadXml(xmlText);
            return doc;
        }
    }
}